package cn.clq.ymcc.service.impl;

import cn.clq.ymcc.domain.Course;
import cn.clq.ymcc.domain.CourseMarket;
import cn.clq.ymcc.domain.KillActivity;
import cn.clq.ymcc.domain.KillCourse;
import cn.clq.ymcc.dto.KillParamDto;
import cn.clq.ymcc.dto.KillPreOrderParamsDto;
import cn.clq.ymcc.mapper.KillCourseMapper;
import cn.clq.ymcc.service.IKillActivityService;
import cn.clq.ymcc.service.IKillCourseService;
import cn.clq.ymcc.util.AssertUtil;
import cn.clq.ymcc.util.CodeGenerateUtils;
import cn.clq.ymcc.vo.CourseItemVo;
import cn.clq.ymcc.vo.CourseOrderVo;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.mapper.Wrapper;
import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Set;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author stone.chen
 * @since 2022-08-04
 */
@Service
public class KillCourseServiceImpl extends ServiceImpl<KillCourseMapper, KillCourse> implements IKillCourseService {


    @Autowired
    private IKillActivityService killActivityService;

    @Autowired
    private RedisTemplate<Object,Object> redisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    /**
     * @description: 给秒杀活动新增课程
     * @param: killCourse
     * @return: cn.clq.ymcc.result.JSONResult
     * @author clqlongbao
     * @date: 2022/8/6 1:03
     */
    @Override
    public void save(KillCourse killCourse) {
        //已经存在的课程不弄重复添加
        KillCourse killCourseTmp = selectByActivityIdAndCourseId(killCourse.getActivityId(),killCourse.getCourseId());
        AssertUtil.isNull(killCourseTmp,"该秒杀课程已经存在");
        Date now = new Date();
        KillActivity killActivity = killActivityService.selectById(killCourse.getActivityId());
        AssertUtil.isNotNull(killActivity,"请传入正确的秒杀活动");
        killCourse.setStartTime(killActivity.getStartTime());
        killCourse.setEndTime(killActivity.getEndTime());
        killCourse.setTimeStr(killActivity.getTimeStr());
        killCourse.setCreateTime(now);
        insert(killCourse);
    }

    /**
     * @description: 根据课程id和活动id查询秒杀课程
     * @param: activityId   courseId
     * @return: cn.clq.ymcc.domain.KillCourse
     * @author clqlongbao
     * @date: 2022/8/6 1:20
     */
    private KillCourse selectByActivityIdAndCourseId(Long activityId, Long courseId) {
        Wrapper<KillCourse> wrapper = new EntityWrapper<>();
        wrapper.eq("activity_id",activityId);
        wrapper.eq("course_id",courseId);
        return selectOne(wrapper);
    }

    /**
     * @description: 查询所有秒杀课程
     * @param:
     * @return: cn.clq.ymcc.result.JSONResult
     * @author clqlongbao
     * @date: 2022/8/6 19:31
     */
    @Override
    public List<KillCourse> onlineAll() {
        ArrayList<KillCourse> killCourseList = new ArrayList<>();
        //先从redis拿到所有的K(活动id)
        Set<Object> keys = redisTemplate.keys("activity:*");
        AssertUtil.isNotNull(keys,"秒杀课程查询错误");
        //通过活动id拿到该活动下所有的课程
        keys.forEach(key->{
            List values = redisTemplate.opsForHash().values(key);
            killCourseList.addAll(values);
        });
        return killCourseList;
    }


    /**
     * @description: 查询秒杀课程
     * @param: activityId   killId
     * @return: cn.clq.ymcc.result.JSONResult
     * @author clqlongbao
     * @date: 2022/8/6 23:52
     */
    @Override
    public KillCourse onlineOne(Long activityId, Long killId) {
        AssertUtil.isNotNull(activityId,"秒杀详情页-参数不能为空");
        AssertUtil.isNotNull(killId,"秒杀详情页-参数不能为空");
        return (KillCourse) redisTemplate.opsForHash().get("activity:" + activityId, killId.toString());
    }


    /**
     * @description: 秒杀下单，返回预创订单的订单号
     * 1.查询出秒杀课程,判断是否在秒杀中以及各种业务判断
     *   登录之后才能秒杀
     *   一个人能不能重复秒杀同一个课程
     *   黑白名单
     * 2.使用信号量扣减库存
     * 3.扣减不成功，给客人一个提示信息
     * 4.如果扣减成功--生成预创订单-保存到redis-返回订单号
     * @param: dto
     * @return: cn.clq.ymcc.result.JSONResult
     * @author clqlongbao
     * @date: 2022/8/7 0:19
     */
    @Override
    public String kill(KillParamDto dto) {
        //1.查询出秒杀课程,判断是否在秒杀中以及各种业务判断
        Long activityId = dto.getActivityId();
        Long killCourseId = dto.getKillCourseId();
        KillCourse killCourse = onlineOne(activityId, killCourseId);
        AssertUtil.isNotNull(killCourse,"请求参数错误");
        AssertUtil.isTrue(killCourse.isKilling(),"该课程不在秒杀时间中");
        //  登录之后才能秒杀
        Long loginId = 3L;
        //  一个人无法重复秒杀同一个课程
        String repeatKey = activityId +":"+killCourseId +":"+loginId;
        Object repeatOrderNo = redisTemplate.opsForValue().get(repeatKey);
        AssertUtil.isNull(repeatOrderNo,"该课程无法重复秒杀");
        //2.使用信号量扣减库存
        String key = activityId+":"+killCourse.getId();
        RSemaphore semaphore = redissonClient.getSemaphore(key);
        //设置秒杀数量
        Integer killCount = 1;
        //扣减信号量
        boolean tryAcquire = semaphore.tryAcquire(killCount);
        //3.扣减不成功，给客人一个提示信息
        AssertUtil.isTrue(tryAcquire,"请稍后重试");
        //4.如果扣减成功--生成预创订单-保存到redis-返回订单号
        //生成订单号
        String orderNo = CodeGenerateUtils.generateOrderSn(loginId);
        //生成预创订单
        KillPreOrderParamsDto killPreOrderParamsDto = new KillPreOrderParamsDto(
                orderNo,
                activityId,
                killCourseId,
                killCount,
                killCourse.getKillPrice(),
                loginId,
                killCourse.getCourseId(),
                killCourse.getCourseName(),
                killCourse.getCoursePic()
        );
        //保存预创订单到redis
        redisTemplate.opsForValue().set(orderNo,killPreOrderParamsDto);
        //存储秒杀成功信息，防止重复秒杀
        redisTemplate.opsForValue().set(repeatKey,orderNo);
        return orderNo;
    }


    /**
     * @description: 查询订单结算页所需数据
     * @param: orderNo
     * @return: cn.clq.ymcc.result.JSONResult
     * @author clqlongbao
     * @date: 2022/8/7 1:03
     */
    @Override
    public CourseOrderVo oneByOrder(String orderNo) {
        //1.从redis中获取预创订单
        KillPreOrderParamsDto preOrder = (KillPreOrderParamsDto) redisTemplate.opsForValue().get(orderNo);
        AssertUtil.isNotNull(preOrder,"请求非法！");
        //2.根据预创订单封装我们想要的Vo结构
        //2.1.根据每个课程Id查询课程 + 销售表
        List<CourseItemVo> courseInfos = new ArrayList<>();
        Course course = new Course();
        course.setId(preOrder.getCourseId());
        course.setName(preOrder.getCourseName());
        course.setPic(preOrder.getCoursePic());
        CourseMarket courseMarket = new CourseMarket();
        courseMarket.setPrice(preOrder.getKillAmount());
        CourseItemVo courseItemVo = new CourseItemVo(course, courseMarket);
        courseInfos.add(courseItemVo);
        //2.2.将封装好的List<CourseItemVo> + 总金额，封装成CourseOrderVo
        CourseOrderVo courserOrderVo = new CourseOrderVo(courseInfos, preOrder.getKillAmount());
        return courserOrderVo;
    }
}
